package cn.bearspark.happening.provider.service.impl;

import cn.bearspark.common.aop.autoparse.host.AutoParseHost;
import cn.bearspark.common.dto.Page;
import cn.bearspark.common.enumeration.ModuleEnum;
import cn.bearspark.common.util.ConvertBeanUtil;
import cn.bearspark.common.util.ConvertInfoUtil;
import cn.bearspark.file.rpc.FileRpc;
import cn.bearspark.happening.dto.happening.ForwardReqDTO;
import cn.bearspark.happening.dto.happening.LCFNumInfoDTO;
import cn.bearspark.happening.dto.happening.ModifyReqDTO;
import cn.bearspark.happening.dto.happening.PublishHappeningReqDTO;
import cn.bearspark.happening.enumeration.happening.AdvanceReleaseEnum;
import cn.bearspark.common.enumeration.CommentAbleEnum;
import cn.bearspark.common.enumeration.VisibilityEnum;
import cn.bearspark.happening.provider.component.IdGeneratorComponent;
import cn.bearspark.happening.provider.dao.buffer.HappeningHashBuffer;
import cn.bearspark.happening.provider.dao.buffer.HappeningIdPageBuffer;
import cn.bearspark.happening.provider.dao.buffer.HappeningImgBuffer;
import cn.bearspark.happening.provider.dao.mapper.*;
import cn.bearspark.happening.provider.dao.po.HappeningBasePO;
import cn.bearspark.happening.provider.dao.po.UserHappeningCntPO;
import cn.bearspark.happening.provider.service.HappeningService;
import cn.bearspark.happening.provider.service.bo.HappeningBO;
import cn.bearspark.happening.provider.dao.co.HappeningCO;
import cn.bearspark.happening.provider.service.bo.ScheduledHappeningBO;
import cn.bearspark.common.enumeration.LikedEnum;
import cn.bearspark.schedule.rpc.ScheduleRpc;
import cn.bearspark.user.rpc.UserAtRpc;
import cn.bearspark.user.rpc.UserBaseRpc;
import cn.bearspark.user.rpc.UserHappeningRpc;
import com.alibaba.fastjson2.JSON;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author f
 */
@Service
public class HappeningServiceImpl implements HappeningService {

    /**
     * 异步修改用户分页查询偏移量的线程池，有 100 个线程
     */
    private final ExecutorService modPageOffsetThreadPool = Executors.newFixedThreadPool(100,
            r -> new Thread(r, "mod-page-offset-thread")
    );

    @DubboReference
    private UserBaseRpc userBaseRpc;
    @DubboReference
    private UserHappeningRpc userHappeningRpc;
    @DubboReference
    private UserAtRpc userAtRpc;
    @DubboReference
    private ScheduleRpc scheduleRpc;
    @DubboReference
    private FileRpc fileRpc;

    private final IdGeneratorComponent idGeneratorComponent;

    private final HappeningBaseMapper happeningBaseMapper;
    private final HappeningContentMapper happeningContentMapper;
    private final HappeningImgMapper happeningImgMapper;

    private final HappeningImgBuffer happeningImgBuffer;
    private final HappeningHashBuffer happeningHashBuffer;
    private final HappeningIdPageBuffer happeningIdPageBuffer;

    public HappeningServiceImpl(IdGeneratorComponent idGeneratorComponent,
                                HappeningBaseMapper happeningBaseMapper,
                                HappeningContentMapper happeningContentMapper,
                                HappeningImgMapper happeningImgMapper,
                                HappeningImgBuffer happeningImgBuffer,
                                HappeningHashBuffer happeningHashBuffer,
                                HappeningIdPageBuffer happeningIdPageBuffer) {
        this.idGeneratorComponent = idGeneratorComponent;
        this.happeningBaseMapper = happeningBaseMapper;
        this.happeningContentMapper = happeningContentMapper;
        this.happeningImgMapper = happeningImgMapper;
        this.happeningImgBuffer = happeningImgBuffer;
        this.happeningHashBuffer = happeningHashBuffer;
        this.happeningIdPageBuffer = happeningIdPageBuffer;
    }

    @Override
    @AutoParseHost(PublishHappeningReqDTO.class)
    public long publish(PublishHappeningReqDTO publishHappeningReqDTO) {
        Long happeningId = idGeneratorComponent.generateId();

        long userId = publishHappeningReqDTO.getUserId();

        HappeningBasePO happeningBasePO = ConvertBeanUtil.convert(publishHappeningReqDTO, HappeningBasePO.class);
        happeningBasePO.setHappeningId(happeningId);
        if (publishHappeningReqDTO.getBookLiveInfo() != null) {
            happeningBasePO.setBookLiveId(happeningId);
        }
        if (publishHappeningReqDTO.getVoteInfo() != null) {
            happeningBasePO.setVoteId(happeningId);
        }

        // 保存动态内容信息
        happeningContentMapper.insert(happeningId, publishHappeningReqDTO.getContent());

        List<String> imgUrlList = publishHappeningReqDTO.getImgUrlList();
        if (!CollectionUtils.isEmpty(imgUrlList)) {
            // 阻止删除上传的图片
            fileRpc.preventDeletion(imgUrlList);

            // 保存动态图片信息
            happeningImgMapper.insertBatch(happeningId, imgUrlList);
        }

        LocalDateTime pubTime = publishHappeningReqDTO.getPubTime();
        if (happeningBasePO.getAdvanceRelease() == AdvanceReleaseEnum.YES.getCode()) {
            // 对于提前发布的动态，做额外操作
            happeningBasePO.setPubTime(pubTime);
            happeningBasePO.setUpdateTime(pubTime);

            userHappeningRpc.afterPublishHappening(userId);
        } else if (LocalDateTime.now().isAfter(pubTime)) {
            // 对于立即发布的动态，做额外操作
            userHappeningRpc.afterPublishHappening(userId);
        } else {
            // 设置定时发布的动态
            happeningBasePO.setPubTime(pubTime);
            Integer visibility = happeningBasePO.getVisibility();
            happeningBasePO.setVisibility(VisibilityEnum.INVISIBLE.getCode());

            // 提交定时发布的任务
            ScheduledHappeningBO scheduledHappeningBO = new ScheduledHappeningBO();
            scheduledHappeningBO.setHappeningId(happeningId);
            scheduledHappeningBO.setVisibility(visibility);
            scheduledHappeningBO.setUserId(userId);
            scheduleRpc.scheduled(ModuleEnum.HAPPENING.getCode(), pubTime, JSON.toJSONString(scheduledHappeningBO));
        }

        // 保存动态的基础信息
        happeningBaseMapper.insert(happeningBasePO);

        // 增加 用户 和 粉丝 的分页偏移量
        updatePageOffset(userId, happeningId, true, true);

        return happeningId;
    }

    @Override
    public Page<HappeningBO> pageQuerySAF(long userId, List<Long> followerIdList,
                                          int pageNum, int pageSize) {
        Page<Long> happeningIdPage =
                happeningIdPageBuffer.pageSelfHappeningIdList(userId, followerIdList, pageNum, pageSize);
        Page<HappeningBO> happeningDTOPage = new Page<>();
        happeningDTOPage.setTotal(happeningIdPage.getTotal());
        happeningDTOPage.setRecords(asmHappeningBOList(
                happeningHashBuffer.listHappeningCO(happeningIdPage.getRecords())
        ));
        return happeningDTOPage;
    }

    @Override
    @AutoParseHost(HappeningBO.class)
    public List<HappeningBO> pageQueryByUserId(long operatorId, long userId, int pageNum, int pageSize) {
        return asmHappeningBOList(happeningHashBuffer.listHappeningCO(
                happeningIdPageBuffer.pageUserHappeningIdList(operatorId, userId, pageNum, pageSize)
        ));
    }

    // 组装动态业务对象集合
    private List<HappeningBO> asmHappeningBOList(List<HappeningCO> happeningCOList) {
        if (happeningCOList.isEmpty()) {
            return new ArrayList<>();
        }

        // 用于查询动态图片的动态 id 集合
        Set<Long> imgHappeningIdSet = happeningCOList.stream()
                .map(HappeningCO::getHappeningId).collect(Collectors.toSet());

        // 获取动态的图片
        Map<Long, List<String>> imgUrlMap = !imgHappeningIdSet.isEmpty() ?
                happeningImgBuffer.map(imgHappeningIdSet) :
                new HashMap<>();

        // 组装业务对象，并返回
        return happeningCOList.stream().map(happeningCO -> {
            long happeningId = happeningCO.getHappeningId();
            String content = happeningCO.getContent();
            if (content == null) {
                return null;
            }
            HappeningBO happeningBO = ConvertInfoUtil.convert(happeningCO, HappeningBO.class);
            happeningBO.setImgUrlList(imgUrlMap.get(happeningId));
            return happeningBO;
        }).filter(Objects::nonNull).toList();
    }

    @Override
    public long forward(ForwardReqDTO forwardReqDTO) {
        long happeningId = idGeneratorComponent.generateId();
        long userId = forwardReqDTO.getUserId();
        long quotedHappeningId = forwardReqDTO.getQuotedHappeningId();

        HappeningBasePO happeningBasePO = genForwardHappening(forwardReqDTO, happeningId);

        // 更新用户的最近活跃时间+增加用户动态数
        userHappeningRpc.afterPublishHappening(userId);

        // 保存动态的基础信息
        happeningBaseMapper.insert(happeningBasePO);

        // 保存动态内容信息
        happeningContentMapper.insert(happeningId, forwardReqDTO.getContent());

        // 如果记录转发了，则需要增加动态的转发数
        if (forwardReqDTO.getRecorded()) {
            happeningBaseMapper.incrForwardNum(quotedHappeningId);
        }

        // 增加 用户 和 粉丝 的分页偏移量
        updatePageOffset(userId, happeningId, true, true);

        // 返回动态 id
        return happeningId;
    }

    // 生成一个转发的动态基础数据对象
    private static HappeningBasePO genForwardHappening(ForwardReqDTO forwardReqDTO, long happeningId) {
        HappeningBasePO happeningBasePO = ConvertBeanUtil.convert(forwardReqDTO, HappeningBasePO.class);
        happeningBasePO.setHappeningId(happeningId);
        // 转发动态的 可见性 默认是 公开 的
        happeningBasePO.setVisibility(VisibilityEnum.PUBLIC.getCode());
        // 转发动态的 可评论性 默认是 可评论 的
        happeningBasePO.setCommentAble(CommentAbleEnum.ABLE.getCode());
        // 转发动态默认是 不提前发布 的
        happeningBasePO.setAdvanceRelease(AdvanceReleaseEnum.NO.getCode());
        return happeningBasePO;
    }

    @Override
    public List<HappeningBO> listQuoteHappeningInfo(Set<Long> quotedHappeningIdSet) {
        return asmHappeningBOList(happeningHashBuffer.listHappeningCO(quotedHappeningIdSet));
    }

    /**
     * 更新 用户(可选) 和 用户粉丝 的动态分页查询偏移量
     *
     * @param userId                   指定的用户 id
     * @param happeningId              更新的动态 id
     * @param needUpdateSelfPageOffset 是否需要更新用户自己的分页查询偏移量
     * @param isIncrement              是否增加分页查询偏移量，如果为 false，表示减少分页查询偏移量
     */
    private void updatePageOffset(long userId, long happeningId,
                                  boolean needUpdateSelfPageOffset, boolean isIncrement) {
        modPageOffsetThreadPool.execute(() -> {
            List<Long> userIdList = userBaseRpc.listFansId(userId);
            if (needUpdateSelfPageOffset) {
                userIdList.add(userId);
            }
            if (isIncrement) {
                happeningIdPageBuffer.incrPageOffset(userIdList, userId);
            } else {
                happeningIdPageBuffer.decrPageOffset(userIdList, happeningId, userId);
            }
        });
    }

    @Override
    public List<HappeningBO> detail(long userId, long happeningId) {
        HappeningCO happeningCO = happeningHashBuffer.getOddly(userId, happeningId);

        // 如果没有权限，则直接返回空集合
        if (happeningCO == null) {
            return new ArrayList<>();
        }

        // 合并 动态 与 其引用的动态
        List<HappeningCO> happeningCOList = new ArrayList<>();
        happeningCOList.add(happeningCO);
        Long quotedHappeningId = happeningCO.getQuotedHappeningId();
        if (quotedHappeningId != null) {
            happeningCOList.add(happeningHashBuffer.getOddly(userId, quotedHappeningId));
        }

        return asmHappeningBOList(happeningCOList);
    }

    @Override
    public void like(long userId, long happeningId, int liked) {
        // 清除动态的热数据
        happeningHashBuffer.cleanHot(happeningId);

        if (liked == LikedEnum.LIKED.getCode()) {
            // 如果 liked 是点赞，则增加点赞数量
            happeningBaseMapper.incrLikeNum(happeningId);
        } else {
            // 如果 liked 是未点赞，则减少点赞数量
            happeningBaseMapper.decrLikeNum(happeningId);
        }
    }

    @Override
    public HappeningCO delete(long userId, long happeningId) {
        HappeningCO happeningCO = happeningHashBuffer.get(happeningId);

        // 如果动态发布者不是执行操作的用户，则说明该用户不是发布者，直接返回
        if (happeningCO.getUserId() != userId) {
            return null;
        }

        // 删除 动态的图片
        List<String> imgUrlList = happeningImgMapper.listByHappeningId(happeningId);
        if (!imgUrlList.isEmpty()) {
            fileRpc.deleteBatch(imgUrlList);
            happeningImgMapper.deleteByHappeningId(happeningId);
            happeningImgBuffer.clean(happeningId);
        }

        // 更新用户最后活跃时间+减少用户的动态数
        userHappeningRpc.afterDeleteHappening(userId, happeningBaseMapper.getLastPubTime(userId));

        // 删除 动态的基础数据
        happeningBaseMapper.deleteById(happeningId);

        // 删除 动态内容数据
        happeningContentMapper.deleteById(happeningId);

        // 删除 动态的缓存数据
        happeningHashBuffer.deleteOddly(happeningId);

        // 减少 用户 和 粉丝 的分页偏移量
        updatePageOffset(userId, happeningId, true, false);

        return happeningCO;
    }

    @Override
    public void modify(ModifyReqDTO modifyReqDTO) {
        long happeningId = modifyReqDTO.getHappeningId();
        HappeningCO happeningCO = happeningHashBuffer.get(happeningId);

        // 如果该用户不是动态的发布者，直接返回即可
        long userId = modifyReqDTO.getUserId();
        if (userId != happeningCO.getUserId()) {
            return;
        }

        int oriVis = happeningCO.getVisibility();
        int tarVis = modifyReqDTO.getVisibility();

        // 更新动态基础数据
        happeningBaseMapper.update(happeningId, modifyReqDTO.getTagId(), modifyReqDTO.getTitle(), tarVis);

        // 更新动态的内容
        happeningContentMapper.update(happeningId, modifyReqDTO.getContent());

        // 如果可见性的变化是 公开 -> 隐私，则对于粉丝相当于 删除 了一条动态
        if (oriVis == VisibilityEnum.PUBLIC.getCode() && tarVis == VisibilityEnum.PRIVATE.getCode()) {
            // 减少 粉丝 的分页偏移量
            updatePageOffset(userId, happeningId, false, false);
        }

        // 如果可见性的变化是 隐私 -> 公开，则对于粉丝相当于 发布 了一条动态
        if (oriVis == VisibilityEnum.PRIVATE.getCode() && tarVis == VisibilityEnum.PUBLIC.getCode()) {
            // 增加 粉丝 的分页偏移量
            updatePageOffset(userId, happeningId, false, true);
        }
    }

    @Override
    public void incrCommentNum(long happeningId) {
        happeningHashBuffer.cleanHot(happeningId);
        happeningBaseMapper.incrCommentNum(happeningId);
    }

    @Override
    public void decrCommentNum(long happeningId, long decrement) {
        happeningHashBuffer.cleanHot(happeningId);
        happeningBaseMapper.decrCommentNum(happeningId, decrement);
    }

    @Override
    public long getLikeNum(long happeningId) {
        return happeningHashBuffer.get(happeningId).getLikeNum();
    }

    @Override
    public long getForwardNum(long happeningId) {
        return happeningHashBuffer.get(happeningId).getForwardNum();
    }

    @Override
    public long getCommentNum(long happeningId) {
        return happeningHashBuffer.get(happeningId).getCommentNum();
    }

    @Override
    public void onHappeningDeadline(List<String> infoList) {
        List<Long> userIdList = new ArrayList<>(infoList.size());

        // 修改可见性
        infoList.stream().map(info -> {
            ScheduledHappeningBO scheduledHappeningBO = JSON.parseObject(info, ScheduledHappeningBO.class);
            long userId = scheduledHappeningBO.getUserId();
            long happeningId = scheduledHappeningBO.getHappeningId();
            userIdList.add(userId);

            // 增加 用户 和 粉丝 的分页偏移量
            updatePageOffset(userId, happeningId, true, true);

            return scheduledHappeningBO;
        }).collect(Collectors.groupingBy(
                ScheduledHappeningBO::getVisibility,
                Collectors.mapping(
                        Function.identity(),
                        Collectors.mapping(ScheduledHappeningBO::getHappeningId, Collectors.toList())
                )
        )).forEach(happeningBaseMapper::updateVisibility);

        // 对于定时发布的动态，等发布后批量修改用户的动态相关信息
        if (!userIdList.isEmpty()) {
            userHappeningRpc.afterPublishScheduledHappening(
                    happeningBaseMapper.countUsersHappening(userIdList, VisibilityEnum.INVISIBLE.getCode()).stream()
                            .collect(Collectors.toMap(
                                    UserHappeningCntPO::getUserId,
                                    UserHappeningCntPO::getHappeningNum
                            )));
        }
    }

    @Override
    public boolean isCommentable(long happeningId) {
        return happeningHashBuffer.get(happeningId).getCommentAble() == CommentAbleEnum.ABLE.getCode();
    }

    @Override
    public LCFNumInfoDTO getLCFNumInfo(long happeningId) {
        return ConvertInfoUtil.convert(happeningHashBuffer.get(happeningId), LCFNumInfoDTO.class);
    }

    @Override
    public Map<Long, LCFNumInfoDTO> mapLCNumInfo(List<Long> happeningIdList) {
        return happeningHashBuffer.map(happeningIdList).entrySet().stream().collect(Collectors.toMap(
                Map.Entry::getKey,
                entry -> ConvertInfoUtil.convert(entry.getValue(), LCFNumInfoDTO.class)
        ));
    }
}
